﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Web.Razor;
using System.Web.Razor.Generator;
using Microsoft.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using PropertyExpression.Common;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using System.Reflection.Emit;

namespace PropertyExpression.ControllableQuery.Test
{
    [TestClass]
    public partial class MainTest
    {
        [TestMethod]
        public void Test21()
        {
            Filter? filter = Filter.Suggested;
            Option<string> search = "%test%";
            var p = new {
                filter, user = new {IsAnonymous = false, IsModerator = false, Id = 1}, tagScoreRequiredToVote = 10,
                search, tab = Tab.Newest, start = 0, pageSize = 20
            };
            var rows = Query<ITagSynonym2>.New(p, @"
SELECT  TagSynonyms.Id, SourceTagName, TargetTagName, OwnerUserId, ApprovalDate, TagSynonyms.CreationDate,
        AutoRenameCount, Score, SourceTags.Id AS SourceTagId, TargetTags.Id AS TargetTagId, Users.DisplayName
@It.MainQuery2(p)
ORDER BY
@switch (tab) {
    case Tab.Newest:
        @:CreationDate DESC, Id
        break;
    case Tab.Master:
        @:TargetTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Synonym:
        @:SourceTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Votes:
        @:Score DESC, TargetTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Creator:
        @:DisplayName, Id
        break;
    case Tab.Renames:
        @:AutoRenameCount DESC, TargetTagName, Id
        break;
}
OFFSET @start ROWS FETCH NEXT @pageSize ROWS ONLY
OPTION (OPTIMIZE FOR (@start = 0, @pageSize = 20))").List();
            var count = Query<int?>.New(p, "SELECT COUNT(*) @It.MainQuery2(p)").Single().Value;
        }

        internal static QFragment MainQuery2(object p)
        {
            return p.QFragment(@"
FROM    TagSynonyms
        LEFT JOIN Tags SourceTags ON SourceTagName = SourceTags.Name
        LEFT JOIN Tags TargetTags ON TargetTagName = TargetTags.Name
        LEFT JOIN Users ON Users.Id = OwnerUserId
WHERE   1 = 1
@switch (filter) {
    case Filter.Active:
        @:AND ApprovalDate IS NOT NULL
        break;
    case Filter.Suggested:
        @:AND ApprovalDate IS NULL
        if (!user.IsAnonymous && !user.IsModerator) {
        @:AND TargetTagName IN (@TargetTagNames())
        }
        break;
    case Filter.Merge:
        @:AND ApprovalDate IS NOT NULL AND ISNULL(SourceTags.Count, 0) > 0
        break;
}
@if (search.HasValue) {
        @:AND (SourceTagName LIKE @search OR TargetTagName LIKE @search)
}
@helper TargetTagNames() {<text>
SELECT  Name
FROM    Tags
WHERE   Id IN (SELECT   Id
               FROM     UserTagTotals
               WHERE    UserId = @user.Id
                        AND TotalAnswerScort > @tagScoreRequiredToVote)
</text>}");
        }

        [TestMethod]
        public void UpdateTest()
        {
            NonQuery.New(new {}, "DELETE T1").Execute();

            var id = 1;
            NonQuery.New(new {
                Id = id, T001 = "qwe3", T002 = "qwe4"
            }, "@p.Insert(\"T1\")").Execute();

            Assert.AreEqual("qwe3",
                Query<string>.New(new {id}, "SELECT T001 FROM T1 WHERE Id = @id").Result().Single());

            NonQuery.New(new {
                Id = id, T001 = "qwe5", T002 = "qwe4"
            }, "@p.Update(\"T1\")").Execute();

            Assert.AreEqual("qwe5",
                Query<string>.New(new {id}, "SELECT T001 FROM T1 WHERE Id = @id").Result().Single());

            NonQuery.New(new {
                Id = id
            }, "@p.Delete(\"T1\")").Execute();

            {
                const bool isNew = true;
                NonQuery.New(new {
                    isNew, param = new {
                        Id = isNew ? default(int) : 2, T001 = "qwe3"
                    }
                }, "@p.InsertOrUpdate(\"T2\")").Execute();
            }

            {
                const bool isNew = false;
                NonQuery.New(new {
                    isNew, param = new {
                        Id = isNew ? default(int) : 2, T001 = "qwe3"
                    }
                }, "@p.InsertOrUpdate(\"T2\")").Execute();
            }

            var single = Query<decimal?>.New(new {
                Id = default(int),  T001 = "qwe3"
            }, "@p.Insert(\"T2\"); SELECT SCOPE_IDENTITY();").Single();
        }

        [TestMethod]
        public void Test019()
        {
            DateTime? date = new DateTime(year: 2013, month: 07, day: 21);
            var result = Query<IPost>.New(new {date}, @"
SELECT  TagSynonyms.Id, CreationDate
FROM    TagSynonyms
WHERE   1 = 1
@MainTest.TestMwthod1(date)").List();
        }

        [TestMethod]
        public void Test018()
        {
            DateTime? date = new DateTime(year: 2013, month: 07, day: 21);
            var result = Query<IPost>.New(new {date}, @"
SELECT  TagSynonyms.Id, CreationDate
FROM    TagSynonyms
WHERE   1 = 1
@TestMwthod1()

@helper TestMwthod1() {
    if (date.HasValue) {
        @:AND CreationDate >= @date
    }
}").List();
        }

        [TestMethod]
        public void Test017()
        {
            DateTime? date = new DateTime(year: 2013, month: 07, day: 21);
            var posts = Query<IPost>.New(new {date}, @"
SELECT  Id, CreationDate
FROM    Post
WHERE   1 = 1
@if (date.HasValue) {
        @:AND CreationDate >= @date
}").List();

            Console.WriteLine(posts.Count);
        }

        [TestMethod]
        public void Test016()
        {
            Test016(false);
            Test016(true);
        }

        private static void Test016(bool someCondition)
        {
            var id = Guid.NewGuid();
            const int age = 10;

            var result = Query<T012>.New(new {
                age, id, someCondition
            }, @"
SELECT Age = @age, Id = @id
@if (someCondition) {<text>
UNION ALL
SELECT Age = @age + 1, Id = @id</text>}").List();

            if (someCondition)
            {
                Assert.AreEqual(2, result.Count);
                Assert.AreEqual(id, result[1].Id);
                Assert.AreEqual(age + 1, result[1].Age);
            }
            else
            {
                Assert.AreEqual(1, result.Count);
            }
            Assert.AreEqual(id, result[0].Id);
            Assert.AreEqual(age, result[0].Age);
        }

        [TestMethod]
        public void Test015()
        {
            var posts = Query<IPost>.New(new {x = "a", y = 1}, "SP1",
                CommandType.StoredProcedure).List();

            Console.WriteLine(posts.Count);
        }

        [TestMethod]
        public void Test020()
        {
            Filter? filter = Filter.Suggested;
            Option<string> search = "%test%";
            var paging = BatchQuery<Paging<ITagSynonym2, int?>>.New(new {
                filter, user = new {IsAnonymous = false, IsModerator = false, Id = 1}, tagScoreRequiredToVote = 10,
                search, tab = Tab.Newest, start = 0, pageSize = 20
            }, @"
SELECT  TagSynonyms.Id, SourceTagName, TargetTagName, OwnerUserId, ApprovalDate, TagSynonyms.CreationDate,
        AutoRenameCount, Score, SourceTags.Id AS SourceTagId, TargetTags.Id AS TargetTagId, Users.DisplayName
@MainQuery()
ORDER BY
@switch (tab) {
    case Tab.Newest:
        @:CreationDate DESC, Id
        break;
    case Tab.Master:
        @:TargetTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Synonym:
        @:SourceTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Votes:
        @:Score DESC, TargetTagName ASC, AutoRenameCount DESC, Id
        break;
    case Tab.Creator:
        @:DisplayName, Id
        break;
    case Tab.Renames:
        @:AutoRenameCount DESC, TargetTagName, Id
        break;
}
OFFSET @start ROWS FETCH NEXT @pageSize ROWS ONLY
OPTION (OPTIMIZE FOR (@start = 0, @pageSize = 20));
SELECT COUNT(*) @MainQuery();
@helper MainQuery() {<text>
FROM    TagSynonyms
        LEFT JOIN Tags SourceTags ON SourceTagName = SourceTags.Name
        LEFT JOIN Tags TargetTags ON TargetTagName = TargetTags.Name
        LEFT JOIN Users ON Users.Id = OwnerUserId
WHERE   1 = 1
@switch (filter) {
    case Filter.Active:
        @:AND ApprovalDate IS NOT NULL
        break;
    case Filter.Suggested:
        @:AND ApprovalDate IS NULL
        if (!user.IsAnonymous && !user.IsModerator) {
        @:AND TargetTagName IN (@TargetTagNames())
        }
        break;
    case Filter.Merge:
        @:AND ApprovalDate IS NOT NULL AND ISNULL(SourceTags.Count, 0) > 0
        break;
}
@if (search.HasValue) {
        @:AND (SourceTagName LIKE @search OR TargetTagName LIKE @search)
}
</text>}
@helper TargetTagNames() {<text>
SELECT  Name
FROM    Tags
WHERE   Id IN (SELECT   Id
               FROM     UserTagTotals
               WHERE    UserId = @user.Id
                        AND TotalAnswerScort > @tagScoreRequiredToVote)
</text>}").Result();
            var rows = paging.Rows;
            var count = paging.Count;

            Console.WriteLine(rows.Count + " " + count);
        }

        [TestMethod]
        public void Test014()
        {
            Filter? filter = Filter.Suggested;
            var paging = Func.Apply(p => Paging2.All(
                p.QFragment(@"
SELECT  TagSynonyms.Id, SourceTagName, TargetTagName, OwnerUserId, ApprovalDate, TagSynonyms.CreationDate,
        AutoRenameCount, Score, SourceTags.Id AS SourceTagId, TargetTags.Id AS TargetTagId, Users.DisplayName
FROM    TagSynonyms
        LEFT JOIN Tags SourceTags ON SourceTagName = SourceTags.Name
        LEFT JOIN Tags TargetTags ON TargetTagName = TargetTags.Name
        LEFT JOIN Users ON Users.Id = OwnerUserId
WHERE   1 = 1
@switch (filter) {
    case Filter.Active:
        @:AND ApprovalDate IS NOT NULL
    break;
    case Filter.Suggested:
        @:AND ApprovalDate IS NULL
        if (!userIsAnonymous && !userIsModerator) {
        @:AND TargetTagName IN (@It.TargetTagNames(userId, tagScoreRequiredToVote))
        }
    break;
    case Filter.Merge:
        @:AND ApprovalDate IS NOT NULL AND ISNULL(SourceTags.Count, 0) > 0
           break;
}
@if (search.HasValue) {
        @:AND (SourceTagName LIKE @search OR TargetTagName LIKE @search)
}"),
                p.QFragment(@"
@switch (tab) {
    case Tab.Newest:
        @:CreationDate DESC
        break;
    case Tab.Master:
        @:TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Synonym:
        @:SourceTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Votes:
        @:Score DESC, TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Creator:
        @:DisplayName
        break;
    case Tab.Renames:
        @:AutoRenameCount DESC, TargetTagName
        break;
}"), p.start, p.finish).BatchQuery<Paging<ITagSynonym, int?>>(), new {
                    filter, userIsAnonymous = false,
                    userIsModerator = false, userId = Param.New(() => 1), tagScoreRequiredToVote = 10.Param(),
                    search = "%test%".Nothing(string.IsNullOrEmpty).Select(_ => _.Param()),
                    tab = Tab.Newest, start = 10.Param(), finish = 20.Param()
                }).Result();
            var rows = paging.Rows;
            var count = paging.Count;
            //TODO: проверить result
        }

        [TestMethod]
        public void Test013()
        {
            var tuple = Func.Apply(p => Paging2.All2(
                p.QFragment(@"
SELECT  TagSynonyms.Id, SourceTagName, TargetTagName, OwnerUserId, ApprovalDate, TagSynonyms.CreationDate,
        AutoRenameCount, Score, SourceTags.Id AS SourceTagId, TargetTags.Id AS TargetTagId, Users.DisplayName
FROM    TagSynonyms
        LEFT JOIN Tags SourceTags ON SourceTagName = SourceTags.Name
        LEFT JOIN Tags TargetTags ON TargetTagName = TargetTags.Name
        LEFT JOIN Users ON Users.Id = OwnerUserId
WHERE   1 = 1
@switch (filter) {
    case Filter.Active:
        @:AND ApprovalDate IS NOT NULL
    break;
    case Filter.Suggested:
        @:AND ApprovalDate IS NULL
        if (!userIsAnonymous && !userIsModerator) {
        @:AND TargetTagName IN (@It.TargetTagNames(userId, tagScoreRequiredToVote))
        }
    break;
    case Filter.Merge:
        @:AND ApprovalDate IS NOT NULL AND ISNULL(SourceTags.Count, 0) > 0
           break;
}
@if (search.HasValue) {
        @:AND (SourceTagName LIKE @search OR TargetTagName LIKE @search)
}"),
                p.QFragment(@"
@switch (tab) {
    case Tab.Newest:
        @:CreationDate DESC
        break;
    case Tab.Master:
        @:TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Synonym:
        @:SourceTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Votes:
        @:Score DESC, TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Creator:
        @:DisplayName
        break;
    case Tab.Renames:
        @:AutoRenameCount DESC, TargetTagName
        break;
}"), p.start, p.finish).BatchQuery<Type<ITagSynonym>, Type<int?>>(), new {
                    filter = Filter.Suggested,
                    userIsAnonymous = false,
                    userIsModerator = false,
                    userId = Param.New(() => 1),
                    tagScoreRequiredToVote = 10.Param(),
                    search = "%test%".Nothing(string.IsNullOrEmpty).Select(_ => _.Param()),
                    tab = Tab.Newest,
                    start = 10.Param(),
                    finish = 20.Param()
                });
            var rows = tuple.Item1.List();
            var count = tuple.Item2.Single().Value;
            //TODO: проверить result
        }

        [TestMethod]
        public void Test012()
        {
            var tuple = Func.Apply(p => Tuple.Create(
                new QWriter().Write(new {}.QFragment("SELECT 1 AS A")),
                new QWriter().Write(new {}.QFragment("SELECT 2 AS B SELECT 3 AS C SELECT 4 AS B"))
                ).BatchQuery<Type<T007>, Type<T008, T010, T008>>(), new { });
            Assert.AreEqual(1, tuple.Item1.Result().Single().A);
            var result2 = tuple.Item2.Result();
            Assert.AreEqual(2, result2.Item1.Single().B);
            Assert.AreEqual(3, result2.Item2.Single().C);
            Assert.AreEqual(4, result2.Item3.Single().B);
        }

        [TestMethod]
        public void Test011()
        {
            var arg = new {
                filter = Filter.Suggested,
                userIsAnonymous = false,
                userIsModerator = false,
                userId = Param.New(() => 1),
                tagScoreRequiredToVote = 10.Param(),
                search = "%test%".Nothing(string.IsNullOrEmpty).Select(_ => _.Param()),
                tab = Tab.Newest,
                start = 10.Param(),
                finish = 20.Param()
            };
            var rows = Func.Apply(p => Paging2.Rows(MainQuery(p),
                p.QFragment(@"
@switch (tab) {
    case Tab.Newest:
        @:CreationDate DESC
        break;
    case Tab.Master:
        @:TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Synonym:
        @:SourceTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Votes:
        @:Score DESC, TargetTagName ASC, AutoRenameCount DESC
        break;
    case Tab.Creator:
        @:DisplayName
        break;
    case Tab.Renames:
        @:AutoRenameCount DESC, TargetTagName
        break;
}"), p.start, p.finish).Query<T006>(), arg).List();
            var count = Func.Apply(p => Paging2.Count(MainQuery(p)).Query<int?>(), arg).Result().Single();
            //TODO: проверить result
        }

        private static QFragment MainQuery(object p)
        {
            return p.QFragment(@"
SELECT  TagSynonyms.Id AS TagSynonymsId, SourceTagName AS TagSynonymsSourceTagName,
        TargetTagName AS TagSynonymsTargetTagName, OwnerUserId, ApprovalDate,
        TagSynonyms.CreationDate AS TagSynonymsCreationDate, AutoRenameCount, Score, SourceTags.Id AS SourceTagId,
        SourceTags.Name AS SourceTagName, TargetTags.Id AS TargetTagId, TargetTags.Name AS TargetTagName,
        Users.Id AS UserId, Reputation, Users.CreationDate, DisplayName, LastAccessDate, WebsiteUrl, Location, Age,
        AboutMe, Views, UpVotes, DownVotes
FROM    TagSynonyms
        LEFT JOIN Tags SourceTags ON SourceTagName = SourceTags.Name
        LEFT JOIN Tags TargetTags ON TargetTagName = TargetTags.Name
        LEFT JOIN Users ON Users.Id = OwnerUserId
WHERE   1 = 1
@switch (filter) {
    case Filter.Active:
        @:AND ApprovalDate IS NOT NULL
    break;
    case Filter.Suggested:
        @:AND ApprovalDate IS NULL
        if (!userIsAnonymous && !userIsModerator) {
        @:AND TargetTagName IN (@It.TargetTagNames(userId, tagScoreRequiredToVote))
        }
    break;
    case Filter.Merge:
        @:AND ApprovalDate IS NOT NULL AND ISNULL(SourceTags.Count, 0) > 0
           break;
}
@if (search.HasValue) {
        @:AND (SourceTagName LIKE @search OR TargetTagName LIKE @search)
}");
        }

        public static QFragment TargetTagNames(Param<int> userId, Param<int> tagScoreRequiredToVote)
        {
            return new {userId, tagScoreRequiredToVote}.QFragment(@"
SELECT  Name
FROM    Tags
WHERE   Id IN (SELECT   Id
               FROM     UserTagTotals
               WHERE    UserId = @userId
                        AND TotalAnswerScort > @tagScoreRequiredToVote)");
        }

        [TestMethod]
        public void Test010()
        {
            var single = Func.Apply(
                p => new QWriter().Write(new {}.QFragment("SELECT 1 Value ORDER BY 1")).Query<T005>(),
                new {}).Result().Single();
        }

        [TestMethod]
        public void ImplCheckTest()
        {
            ImplChecker.Check(GetType().Assembly);
        }

        [TestMethod]
        public void Test001()
        {
            //const int a1 = 1;
            //const string a2 = "Test1";
            //var single = new Query<T001>(
            //    "SELECT @A1 A1, @A2 A2", new {
            //        A1 = a1.Param(),
            //        A2 = a2.Param()
            //    }).Result().Single();
            //Assert.AreEqual(a1, single.A1);
            //Assert.AreEqual(a2, single.A2);
        }

        //[TestMethod]
//        public void Test005()
//        {
//            var result = new Query<T002, T003>(@"
//SELECT DISTINCT
//        t1.ID,
//        STUFF((@Conc()), 1, 1, '') as Value
//FROM    #tmp01 t1
//
//@helper Conc() {
//    <text>
//SELECT  '@' + t2.StrVal AS [text()]
//FROM    #tmp01 t2
//WHERE   t1.ID = t2.ID
//FROM XML path ('')
//    </text>
//}
//
//
//@helper Order() {
//    <text>
//SELECT OrderId, OrderDate FROM Order WHERE CustomerId = @CustomerId
//    </text>
//}
//@Order()
//SELECT OrderLine.OrderId, ProductId, Quantity
//FROM OrderLine JOIN (@Order()) Order ON OrderLine.OrderId = Order.OrderId",
//                new {CustomerId = 77}).Result();
//            //TODO: проверить result
//        }

//        [TestMethod]
//        public void Test006()
//        {
//            var result = new Query<T004>(@"
//Paging(
//    @<text>SELECT * FROM Permission</text>,
//    @<text>Name DESC, PermissionId</text>,
//    @Start, @Finish)", new {
//                Start = 1,
//                Finish = 1
//            }).Result();
//            //TODO: проверить result
//        }

//        [TestMethod]
//        public void Test007()
//        {
//            var result = Func.Apply(p => {
//                var builder = new QBuilder(@"
//SELECT  PersonId,
//        FirstName,
//        LastName
//FROM    1 = 1");
//                if (p.FirstName.HasValue) builder.Append(@"
//        AND FirstName = ", p.FirstName.Value);
//                if (p.LastName.HasValue) builder.Append(@"
//        AND LastName = ", p.LastName.Value);
//                return builder.ToQuery<T004>();
//            }, new {
//                FirstName = "Test1".AsOption().Select(_ => _.Param()),
//                LastName = "Test2".AsOption().Select(_ => _.Param())
//            }).Result();
//            //TODO: проверить result
//        }

//        public void Test008()
//        {
//            var result = new Query<T004>(@"
//SELECT  PersonId, Name
//FROM    Person
//WHERE   1 = 1
//@if (someCondition) {<text>
//        AND Name = @name</text>}", new {
//                name = "test",
//                someCondition = true
//            }).Result();
//            //TODO: проверить result
//        }

//        public void Test009()
//        {
//            var name = Console.ReadLine();
//            new Query<T004>(@"q
//w                                    e", new {
//                a = 1
//            });
//            //var result = new Query<T004>("",
//            //    new {
//            //                         name,
//            //                         nameHasVlue = !string.IsNullOrEmpty(name)
//            //                     }).Result();
//            //TODO: проверить result
//        }

        //TODO: пофиксить The instance of IQuery is ambiguous для Query<>.New

        //TODO: сделать int вместо ICount

        //TODO: заменить Type<> на Tuple<>

        //TODO: QBuilder.Append(string) переименовать в AppendString. Через анализ тел методов сделать проверку, что в аргумент метода QItem.implicit(string) приходят только строковые константы.

        //TODO: сделать так, чтобы при ошибке выдавался код интерфейса для резалт сета

        //TODO: этот CQ взят из другого места, поэтому нужно посмотреть, что я делал
        //в Property Expression по проекту с того времени, как он отпочкавался в другое место.
        //В другое место он отпачкавывался -- переход с 4.0 на 3.5 фреймворк.
        //Вероятно, в этой ветке нет (а в той есть) более удобная выдвча QueryName при ошибках

        //TODO: проверить типы параметров в тестовом запросе от Dapper

        //TODO: Сделать NuGet пакет

        //TODO: реализовать когда ToQuery может возвращать несколько QBuilder-ов, см $/PropertyExpression/ControllableQuery/DevBranches/Razor4.E1.Impl1.TypeT1T2

        //TODO: поддержать хранимые процедуры

        //TODO: подписать сборки, в том числе динамическую

        //TODO: Сделать и реализовать -- когда на вход запроса подается другой IQuery<T>.
        //Checker должен подставлять IQuery<T> одни или все с таким T.

        //TODO: сделать так, чтобы не требовалась установка Roslyn

        //TODO: Сделапть и реализовать тестс SqlCommand.Prepare

        //[TestMethod]
        public void Test004()
        {
            Test003(@"SELECT  PersonId,
        FirstName,
        LastName
FROM    1 = 1
@FirstName");
        }

        private void Test003(string arg)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start();
            Test002(arg);
            stopwatch.Stop();
            Console.WriteLine(stopwatch.ElapsedMilliseconds);
        }

        private void Test002(string s)
        {
            //var arg = new {
            //    A = OptionalValue.Nothing<int>(),
            //    //B = new {A = 2}
            //};

            var arg = new {
                FirstName = new {A = 1},
            };

            var properties = arg.GetType().GetProperties();
//            var format = string.Format(@"{1}
//@functions
//{{
//{0}
//}}",
//                string.Join(@"
//", properties
//                    .Select(_ => "    public " + ConverType(_.PropertyType) + " " + _.Name + ";").ToArray()),
//                s);

            //TODO: пересмотреть код ниже

            // a. Use the C# language (you could detect this based on the file extension if you want to)
            //TODO: временно
            var @default = GeneratedClassContext.Default;
            var context = new GeneratedClassContext(
                @default.ExecuteMethodName, @default.WriteMethodName, @default.WriteLiteralMethodName,
                "WriteTo", "WriteLiteralTo",
                typeof(QFragment).FullName,
                @default.DefineSectionMethodName);

            //TODO: CSharpRazorCodeLanguage обернуть в using
            RazorEngineHost host = new RazorEngineHost(new CSharpRazorCodeLanguage()) {
                GeneratedClassContext = context,
                //StaticHelpers = true
            };

            // b. Set the base class
            host.DefaultBaseClass = typeof (TemplateBase2).FullName;

            // c. Set the output namespace and type name
            host.DefaultNamespace = "RazorOutput";
            //TODO: сделать случайным имя
            host.DefaultClassName = "Template";

            // d. Add default imports
            host.NamespaceImports.Add("System");

            // Create the template engine using this host
            var engine = new RazorTemplateEngine(host);

            GeneratorResults razorResult;
            using (TextReader reader = new StringReader(s))
                razorResult = engine.GenerateCode(reader, className: null, rootNamespace: null, sourceFileName: "qwe.txt");

            if (!razorResult.Success)
            {
                var error = razorResult.ParserErrors.First();
                throw new ApplicationException(string.Format("Razor parsing error: ({0}, {1}) {2}",
                    error.Location.LineIndex, error.Location.CharacterIndex, error.Message));
            }

            StringWriter stringWriter;
            using (stringWriter = new StringWriter())
            using (var codeProvider = new CSharpCodeProvider())
                codeProvider.GenerateCodeFromCompileUnit(
                    razorResult.GeneratedCode, stringWriter, new CodeGeneratorOptions());

            var assemblyName = new AssemblyName { Name = "CQ57" };

            var propertyType = arg.GetType().GetProperties()[0].PropertyType;
            var types = new List<Type>();
            var declaringType = GetTypeSyntax(propertyType, types);

            var tree = SyntaxTree.ParseText(@"
" + stringWriter + @"
namespace RazorOutput
{
" + string.Join(@"
",types.Select((_, i) => "    extern alias r" + i + ";")) + @"

    public partial class Template
    {
        internal System.Object FirstName;
        
        void M1() {
            var arg = new {
                FirstName = new {A = 1},
            };
        }
    }
}
");
            var root = tree.GetRoot();
            Print(root);

            SyntaxNode syntaxNode = root.ChildNodes().Skip(1).First();

            var variableDeclarationSyntax = syntaxNode.DescendantNodes().OfType<VariableDeclarationSyntax>().First();

            //SyntaxNodeOrToken

            //var qualifiedNameSyntax = Syntax.QualifiedName(
            //    Syntax.IdentifierName("System"),
            //    Syntax.IdentifierName("Int32")
            //    );
            //var separatedSyntaxList = Syntax.SeparatedList<TypeSyntax>(
            //    Syntax.Token(SyntaxKind.LessThanToken),
            //    qualifiedNameSyntax,
            //    qualifiedNameSyntax,
            //    Syntax.Token(SyntaxKind.GreaterThanToken)
            //    );

            var newSyntax = variableDeclarationSyntax.WithType(
                declaringType
                );

            Print(declaringType);

            var newTree = SyntaxTree.Create(root.ReplaceNode(variableDeclarationSyntax, newSyntax));

            Print(newTree.GetRoot());

            var compilation = Compilation.Create(
                assemblyName.Name, new CompilationOptions(OutputKind.DynamicallyLinkedLibrary),
                new[] {
                    newTree
                },
                types.Select((_, i) => new MetadataFileReference(_.Assembly.Location,
                    new MetadataReferenceProperties(alias: "r" + i))).Concat(
                        new[] {
                            MetadataReference.CreateAssemblyReference("mscorlib"),
                            new MetadataFileReference(GetType().Assembly.Location),
                            new MetadataFileReference(typeof (Option<>).Assembly.Location)
                        }));

            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(
                assemblyName, AssemblyBuilderAccess.RunAndSave);
            var moduleBuilder = assembly.DefineDynamicModule(assemblyName.Name, assemblyName.Name + ".dll");

            var emitResult = compilation.Emit(moduleBuilder);

            foreach (var d in emitResult.Diagnostics)
            {
                var lineSpan = d.Location.GetLineSpan(usePreprocessorDirectives: true);
                var startLine = lineSpan.StartLinePosition.Line;
                Console.WriteLine("Line {0}: {1}", startLine, d.Info.GetMessage());
            }

            //TODO: временно
            var instance = (TemplateBase2)Activator.CreateInstance(assembly.GetType("RazorOutput.Template"));
            var type = instance.GetType();
            foreach (var property in properties)
                type.GetField(property.Name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic).SetValue(instance, property.GetValue(arg));
            instance.Execute();
            Console.WriteLine(instance.StringBuilder);
            assembly.Save(assemblyName.Name + ".dll");
        }

        private static TypeSyntax GetTypeSyntax(Type type, List<Type> list)
        {
            var syntax = !type.IsGenericType
                ? (SimpleNameSyntax) Syntax.IdentifierName(type.Name)
                : Syntax.GenericName(
                    Syntax.Identifier(type.Name.Substring(0, type.Name.IndexOf("`", StringComparison.Ordinal))),
                    Syntax.TypeArgumentList(
                        Syntax.SeparatedList<TypeSyntax>(
                            WithComma(type.GetGenericArguments().Select(_ => GetTypeSyntax(_, list))).ToArray()
                            )
                        )
                    );
            if (type.Namespace != null)
                return Syntax.QualifiedName(
                    Syntax.ParseName(type.Namespace),
                    syntax
                    );
            else if (DynamicMaterializer.IsAnonymousType(type))
            {
                var result = Syntax.AliasQualifiedName("r" + list.Count, syntax);
                list.Add(type);
                return result;
            }
            else
                return syntax;
        }

        private static IEnumerable<SyntaxNodeOrToken> WithComma(IEnumerable<TypeSyntax> enumerable)
        {
            var isFirst = true;
            foreach (var typeSyntax in enumerable)
            {
                if (!isFirst)
                    yield return Syntax.Token(SyntaxKind.CommaToken);
                else
                    isFirst = false;
                yield return typeSyntax;
            }
        }

        private string ConverType(Type type)
        {
            return DynamicMaterializer.IsAnonymousType(type) ? "object" : type.FullName;
        }

        private static void Print(SyntaxNode arg, string prefix = "")
        {
            Console.WriteLine(prefix + arg.GetType().Name);
            foreach (var syntaxNode in arg.ChildNodes())
            {
                Print(syntaxNode, prefix + "  ");
            }
        }


        /// <summary>
        /// //TODO: удалить
        /// </summary>
        /// <param name="codeCompileUnit"></param>
        public static void qwe(CodeCompileUnit codeCompileUnit)
        {
            var codeProvider = new Microsoft.CSharp.CSharpCodeProvider();

            using (StringWriter sw = new StringWriter())
            {
                codeProvider.GenerateCodeFromCompileUnit(codeCompileUnit, sw, new System.CodeDom.Compiler.CodeGeneratorOptions());
                var s = sw.GetStringBuilder().ToString();
            }
        }

        /// <summary>
        /// //TODO: такой же метод используется в PropertyExpression.CommonюImplChecker. Объеденить.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string ToCSharp(Type type)
        {
            var stringBuilder = new StringBuilder();
            using (var stringWriter = new StringWriter(stringBuilder))
            using (var codeDomProvider = CodeDomProvider.CreateProvider("C#"))
            {
                var codeTypeReferenceExpression = new CodeTypeReferenceExpression(type);
                codeDomProvider.GenerateCodeFromExpression(
                    codeTypeReferenceExpression, stringWriter, new CodeGeneratorOptions());
            }
            return stringBuilder.ToString();
        }

        static MainTest()
        {
            RegisterRazorConfiguration();
        }

        private static void RegisterRazorConfiguration([CallerFilePath] string filePath = "")
        {
            RazorConfiguration.Register(filePath, new [] {
                "PropertyExpression.ControllableQuery.Test"
            }, MethodBase.GetCurrentMethod());
        }
    }

    public class RazorEngineHost : System.Web.Razor.RazorEngineHost
    {
        public RazorEngineHost(RazorCodeLanguage codeLanguage) : base(codeLanguage)
        {
        }

        public override RazorCodeGenerator DecorateCodeGenerator(RazorCodeGenerator incomingCodeGenerator)
        {
            var decorateCodeGenerator = base.DecorateCodeGenerator(incomingCodeGenerator);
            decorateCodeGenerator.GenerateLinePragmas = true;
            return decorateCodeGenerator;
        }

        public override void PostProcessGeneratedCode(CodeGeneratorContext context)
        {
            base.PostProcessGeneratedCode(context);
            context.GeneratedClass.IsPartial = true;
        }
    }

    //public class SubQueryResult
    //{
    //    public readonly Action<StringBuilder> Action;

    //    public SubQueryResult(Action<StringBuilder> action)
    //    {
    //        Action = action;
    //    }
    //}

    /// <summary>
    /// TODO: temp
    /// </summary>
    public abstract class TemplateBase2
    {
        public readonly StringBuilder StringBuilder = new StringBuilder();

        public abstract void Execute();

        /// <summary>
        /// //TODO: удалить
        /// </summary>
        protected void Write(object arg)
        {
            StringBuilder.Append(arg);
        }

        /// <summary>
        /// //TODO: удалить
        /// </summary>
        protected void WriteLiteral(string arg)
        {
            StringBuilder.Append(arg);
        }

        protected static void WriteLiteralTo(StringBuilder stringBuilder, string arg)
        {
            stringBuilder.Append(arg);
        }

        protected static void WriteTo(StringBuilder stringBuilder, int arg)
        {
            stringBuilder.Append(arg);
        }

        protected static void WriteTo(StringBuilder stringBuilder, DateTime arg)
        {
            stringBuilder.Append(arg);
        }

        protected void Write(QFragment qFragment)
        {
            throw new NotImplementedException();
            //subQueryResult.Action(StringBuilder);
        }
    }
}

namespace TestNamespace
{
    public class TestClass<T, T2>
    {        
    }
}